module butterfly_unit
#(
  parameter DATA_WIDTH = 32
)(
  //OUTPUTS
  output signed [DATA_WIDTH - 1:0] x_out,     //Butterfly Unit output
  output signed [DATA_WIDTH - 1:0] y_out,     //Butterfly Unit output
  output ready,                               //Indicates that computation is ready
  //INPUTS
  input signed [DATA_WIDTH - 1:0] x_in,       //Butterfly Unit input
  input signed [DATA_WIDTH - 1:0] y_in,       //Butterfly Unit input
  input signed [DATA_WIDTH/2-1:0] teta_in,    //Butterfly Unit angle input
  input exc_sel0,                             //Exchange Input selector
  input exc_sel1,                             //Exchange Output selector
  input start,                                //Start operation
  input operation,                            //Select operation for FFT(high level) or IFFT(low level)
  input clk,                                  //Clock
  input rst_n                                 //Reset, active low, asynchronous
);

`include "fft_functions.v"

//Real and Imaginary data size
localparam RI_WIDTH = DATA_WIDTH/2;

wire [DATA_WIDTH - 1:0] x, y;
wire [DATA_WIDTH - 1:0] x_butt, y_butt;
wire [RI_WIDTH   - 1:0] x_real, x_imag, y_real, y_imag;
wire [RI_WIDTH   - 1:0] x_butt_real, x_butt_imag, y_butt_real, y_butt_imag;
wire [RI_WIDTH   - 1:0] x_cordic, y_cordic, x_cordic_out, y_cordic_out;
wire [RI_WIDTH   - 1:0] arc_tg;
wire [clogb2(RI_WIDTH)- 1:0] count;

//The input multiplexors: exc_sel = 1 => the inputs are exchanged
assign x = (exc_sel0) ? y_in : x_in;
assign y = (exc_sel0) ? x_in : y_in;

//The output multiplexors: exc_sel = 1 => the outputs are exchanged
assign x_out = (exc_sel1) ? y_butt : x_butt;
assign y_out = (exc_sel1) ? x_butt : y_butt;

//Real and Imaginary parts definitions
assign x_real = x[DATA_WIDTH  - 1 : DATA_WIDTH/2];
assign x_imag = x[DATA_WIDTH/2 -1 : 0];

assign y_real = y[DATA_WIDTH  - 1 : DATA_WIDTH/2];
assign y_imag = y[DATA_WIDTH/2 -1 : 0];

//Butterfly calculations
assign x_butt_real = (operation) ? (x_real + y_real) : (x_real + y_cordic_out) >>> 1;
assign x_butt_imag = (operation) ? (x_imag + y_imag) : (x_imag + x_cordic_out) >>> 1;

assign y_butt_real = (operation) ? x_cordic_out : (x_real - y_cordic_out) >>> 1;
assign y_butt_imag = (operation) ? y_cordic_out : (x_imag - x_cordic_out) >>> 1;

//Module Output
assign x_butt = {x_butt_real, x_butt_imag};
assign y_butt = {y_butt_real, y_butt_imag};

//Cordic Input
assign x_cordic = (operation) ? (x_real - y_real) : y_imag; //See Discrete-time signal processing
assign y_cordic = (operation) ? (x_imag - y_imag) : y_real; //Problem 9.23

//Cordic Unit Instanciation
cordic_unit 
#(
  .DATA_WIDTH(RI_WIDTH)
)
  CORDIC
(
  .x_out    ( x_cordic_out ),
  .y_out    ( y_cordic_out ),
  .ready    ( ready        ),
  .x_in     ( x_cordic     ),
  .y_in     ( y_cordic     ),
  .teta_in  ( teta_in      ),
  .count    ( count        ),
  .arc_tg   ( arc_tg       ),
  .start    ( start        ),
  .clk      ( clk          ),
  .rst_n    ( rst_n        )
);

assign arc_tg = arc_tg_norm(count);

//Normalized format
function [15:0] arc_tg_norm;
  input [3:0] i;
  begin
    case (i)
      4'd0 : arc_tg_norm = 16'd8192;
      4'd1 : arc_tg_norm = 16'd4836;
      4'd2 : arc_tg_norm = 16'd2555;
      4'd3 : arc_tg_norm = 16'd1297;
      4'd4 : arc_tg_norm = 16'd651;
      4'd5 : arc_tg_norm = 16'd326;
      4'd6 : arc_tg_norm = 16'd163;
      4'd7 : arc_tg_norm = 16'd81;
      4'd8 : arc_tg_norm = 16'd41;
      4'd9 : arc_tg_norm = 16'd20;
      4'd10: arc_tg_norm = 16'd10;
      4'd11: arc_tg_norm = 16'd5;
      4'd12: arc_tg_norm = 16'd3;
      4'd13: arc_tg_norm = 16'd1;
      4'd14: arc_tg_norm = 16'd1;
      4'd15: arc_tg_norm = 16'd0;
    endcase
  end
endfunction

endmodule
